﻿package com.moonshineproject.plugin.fdb {
	import com.moonshineproject.events.OpenFileEvent;
	import com.moonshineproject.IDEModel;
	import com.moonshineproject.editor.BasicTextEditor;
	import com.moonshineproject.events.CloseTabEvent;
	import com.moonshineproject.plugin.IPlugin;
	import com.moonshineproject.plugin.PluginBase;
	import com.moonshineproject.plugin.console.ConsoleCommandEvent;
	import com.moonshineproject.plugin.console.view.ConsoleModeEvent;
	import com.moonshineproject.plugin.events.EditorPluginEvent;
	import com.moonshineproject.plugin.menu.MenuPlugin;
	import com.moonshineproject.plugin.mxmlc.MXMLCPluginEvent;
	import com.moonshineproject.plugin.project.vo.ProjectVO;
	import com.moonshineproject.vo.Settings;
	
	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.events.Event;
	import flash.events.NativeProcessExitEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.utils.IDataInput;
	import flash.utils.IDataOutput;
	
	import no.doomsday.console.ConsoleUtil;
	
	public class FDBPlugin extends PluginBase implements IPlugin 
	{
		override public function get name():String			{ return "Flex Debugger Plugin"; }
		override public function get author():String		{ return "Miha Lunar & Moonshine Project Team"; }
		override public function get description():String	{ return "Debugs AS3 projects with FDB."; }
		
		
		private const CONSOLE_MODE:String = "fdb";		
		
		private var cookie:Object;
		
		private var fdbPath:String = "bin/fdb";
		private var currentSDK:File;
		private var fdb:NativeProcess;
		private var debuggerInfo:NativeProcessStartupInfo;
		private var outputBuffer:String = "";
		private var activeMode:Boolean = false;
		
		public function FDBPlugin() 
		{
			if (Settings.os == "win") fdbPath += ".exe";
		}
		
		override public function activate():void 
		{
			super.activate();
			
			dispatcher.addEventListener(MXMLCPluginEvent.PREBUILD, prebuild);
			dispatcher.addEventListener(EditorPluginEvent.EVENT_EDITOR_OPEN, handleEditorOpen);
			dispatcher.addEventListener(MenuPlugin.MENU_SAVE_EVENT, handleEditorSave);
			dispatcher.addEventListener(MenuPlugin.MENU_SAVE_AS_EVENT, handleEditorSave);
			dispatcher.addEventListener(CloseTabEvent.EVENT_CLOSE_FILE, handleEditorSave);
			dispatcher.addEventListener(MXMLCPluginEvent.PREBUILD, handleCompile);
			
			registerCommand(CONSOLE_MODE, fdbCommand); 
			
			cookie = {};
		}
		
		override public function deactivate():void 
		{
			super.deactivate();
			dispatcher.removeEventListener(MXMLCPluginEvent.PREBUILD, prebuild);
			
			unregisterCommand(CONSOLE_MODE);
			
			if (fdb) fdb.exit();
		}
		
		private function handleEditorOpen(event:EditorPluginEvent):void
		{
			if (!event.file) return;
			
			var path:String = event.file.nativePath;
			var breakpoints:Array = cookie[path] as Array;
			if (breakpoints)
			{
				event.editor.breakpoints = breakpoints;		
			}
		}
		
		private function handleEditorSave(event:Event):void
		{
			var editor:BasicTextEditor;
			if (event is CloseTabEvent)
			{
				editor = CloseTabEvent(event).tab as BasicTextEditor;
			}
			else
			{
				editor = IDEModel.getInstance().activeEditor as BasicTextEditor;
			}
			
			saveForEditor(editor);
		}
		
		private function handleCompile(event:Event):void
		{
			// Make sure we have stuff from all editors. (even unsaved?)
			var editors:Array = IDEModel.getInstance().editors.source;
			for (var i:int = 0; i < editors.length; i++)
			{
				saveForEditor(editors[i] as BasicTextEditor);
			}
		}
		
		private function saveForEditor(editor:BasicTextEditor):void
		{
			if (!editor) return;
			if (!editor.currentFile) return;
			
			var path:String = editor.currentFile.nativePath;
			if (path == "") return;
			
			cookie[path] = editor.getEditorComponent().breakpoints;
		}
		
		private function fdbCommand(args:Array):void 
		{		
			if (args.length == 0) 
			{
				enterConsoleMode(CONSOLE_MODE);
				activeMode = true;
			} 
			else if (args[0] == "exit") 
			{
				exitConsoleMode();
				activeMode = false;
			} 
			else if (args[0] == "test") {
				for (var file:String in cookie) {
					print("cookie: "+file+" "+cookie[file]);
				}
			}
			else
			{
				print("FDB "+args);
				if (!fdb) 
				{
					print("FDB not running, please build the project you want to debug at least once.");
				} 
				else 
				{
					send(args.join(" "));
				}
			}
		}
		
		private function initDebugger():void 
		{
			debuggerInfo = new NativeProcessStartupInfo();
			debuggerInfo.executable = currentSDK.resolvePath(fdbPath);
			
			fdb = new NativeProcess();
			fdb.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, debuggerData);
			fdb.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, debuggerError);
			fdb.addEventListener(NativeProcessExitEvent.EXIT, debuggerExit);
			fdb.start(debuggerInfo);
		}
		
		private function debuggerData(e:ProgressEvent):void 
		{
			var output:IDataInput = fdb.standardOutput;
			var data:String = output.readUTFBytes(output.bytesAvailable);
			
			var match:Array;
			
			var project:ProjectVO = IDEModel.getInstance().activeProject;
			
			match = data.match(/.*Player connected; session starting\..*/);
			if (match) {
				send("delete");
				send("y");
				for (var path:String in cookie) {
					var f:File = new File(path);
					send("cf \""+project.file.getRelativePath(f)+"\"");
					var breakpoints:Array = cookie[path];
					for (var i:int = 0; i < breakpoints.length; i++) {
						send("break "+(breakpoints[i]+1));
					}
				}
				if (!activeMode) send("continue");
			}
			
			match = data.match(/Breakpoint (\d*?), (.*?) at (.*?):(\d*).*?/);
			if (match) {
				var bpNum:int = int(match[1]);
				var bpFunc:String = match[2];
				var bpFile:String = match[3];
				var bpLine:int = int(match[4])-1;
				dispatcher.dispatchEvent(new OpenFileEvent(OpenFileEvent.OPEN_FILE, project.file.resolvePath(bpFile), bpLine));
				print("Breakpoint in "+bpFunc+" at line "+bpLine+" of "+bpFile);
			}
			
			match = data.match(/\[trace\] (.*)\n/s);
			if (match) {
				print(match[1]);
			} else {
				outputBuffer += data;
			}
			
			
			//if (data.charAt(data.length-1) == "\n") data = data.substr(0, data.length-1);
			
			match = outputBuffer.match(/(.*)\n\(fdb\) /s);
			if (match) {
				if (activeMode) print("fdb> "+match[1]);
				outputBuffer = "";
			}
			
			ConsoleUtil.print(">>>"+data+"<<<");
		}
		
		private function debuggerError(e:ProgressEvent):void 
		{
			var output:IDataInput = fdb.standardOutput;
			var data:String = output.readUTFBytes(output.bytesAvailable);
			
			if (data.charAt(data.length-1) == "\n") data = data.substr(0, data.length-1);
			if (activeMode) 
			{
				print("fdb> "+data);
			}
			
			ConsoleUtil.addErrorMessage("fdb> "+data);
		}
		
		private function debuggerExit(e:NativeProcessExitEvent):void 
		{
			ConsoleUtil.print("FDB exit code "+e.exitCode);
			fdb = null;
		}
		
		private function prebuild(e:MXMLCPluginEvent):void 
		{
			currentSDK = e.sdk;
			if (!fdb) initDebugger();
			send("run");
		}
		
		private function send(msg:String):void 
		{
			ConsoleUtil.print("Send to fdb: "+msg);
			var input:IDataOutput = fdb.standardInput;
			input.writeUTFBytes(msg+"\n");
		}
	}

}